    using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using DirectShowLib;
using System.Threading;
using System.Reflection;
using Lizk.SimpleHUD;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.Windows.Forms;

namespace SharpMedia.MDX
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class Allocator2 : IVMRSurfaceAllocator9, IVMRImagePresenter9, IDisposable
    {
        // Constants
        private const int E_FAIL = unchecked((int) 0x80004005);
        private const int D3DERR_INVALIDCALL = unchecked((int)0x8876086c);
        private const int DxMagicNumber = -759872593;
        private IntPtr unmanagedSurface = IntPtr.Zero;

        private Size textureSize;
        private Size videoSize;
        private bool needCopy,disposed;
        private Device device = null;
        private AdapterInformation adapterInfo = null;
        private DeviceCreationParameters creationParameters;
        private Surface videoSurface = null;
        private Texture privateTexture = null;
        private Surface privateSurface = null;
        private VMR9AllocationInfo lpAllocInfo;
        private IVMRSurfaceAllocatorNotify9 vmrSurfaceAllocatorNotify = null;

        // Handy Properties to retrieve the texture, its size and the video size
        public Size VideoSize { get { return videoSize; } }

        private object allocatorLock = new object();

        private bool hasImage;
        public bool Closing = false, Ready = false;





        public Texture GetImage()
        {

            Monitor.Enter(allocatorLock);
            
                needTexture = true;

                Monitor.PulseAll(allocatorLock);
        
                if(video.Player.State == Lizk.DShowPlayer.PlayerState.Playing || video.Player.State == Lizk.DShowPlayer.PlayerState.Menu)
                    Monitor.Wait(allocatorLock, 100);
                needTexture = false;

            Monitor.Exit(allocatorLock);
            return privateTexture;
        }

        MDXVideo video;

        public Allocator2(MDXForm form, MDXVideo video)
        {
            this.device = form.GraphicsDevice;
            adapterInfo = form.CurrentAdapter; creationParameters = form.GraphicsDevice.CreationParameters;
            this.video = video;
            
        }

        ~Allocator2()
        {
            Dispose();
        }

        public void Dispose()
        {
            lock (allocatorLock)
            {
                if (!disposed)
                {
                    DeleteSurfaces();
                    disposed = true;
                }
            }
        }

        public void Close()
        {
            lock (allocatorLock)
            {
                Closing = true;
                Monitor.PulseAll(allocatorLock);
            }
        }

       
        public void CreateSurfaces(VMR9AllocationInfo lpAllocInfo)
        {
            lock (allocatorLock)
            {

                // if format is YUV ?
                if (lpAllocInfo.Format > 0x30303030)
                {
                    // An offscreen surface must be created
                    lpAllocInfo.dwFlags |= VMR9SurfaceAllocationFlags.OffscreenSurface |
                                           VMR9SurfaceAllocationFlags.DXVATarget;


                    // Create it
                    videoSurface =
                        device.CreateOffscreenPlainSurface(lpAllocInfo.dwWidth, lpAllocInfo.dwHeight,
                                                           (Format)lpAllocInfo.Format, (Pool)lpAllocInfo.Pool);
                    // And get it unmanaged pointer
                    unmanagedSurface = videoSurface.GetObjectByValue(DxMagicNumber);

                    // Then create a private texture for the client application
                    privateTexture = new Texture(
                        device,
                        lpAllocInfo.dwWidth,
                        lpAllocInfo.dwHeight,
                        1,
                        Usage.RenderTarget,
                        adapterInfo.CurrentDisplayMode.Format,
                        Pool.Default
                        );

                    // Get the MipMap surface 0 for the copy (see PresentImage)
                    privateSurface = privateTexture.GetSurfaceLevel(0);

                    // This code path need a surface copy
                    needCopy = true;
                }
                else
                {
                    // in RGB pixel format
                    lpAllocInfo.dwFlags |= VMR9SurfaceAllocationFlags.TextureSurface |
                                           VMR9SurfaceAllocationFlags.DXVATarget;

                    // Simply create a texture
                    privateTexture = new Texture(
                        device,
                        lpAllocInfo.dwWidth,
                        lpAllocInfo.dwHeight,
                        1,
                        Usage.RenderTarget,
                        adapterInfo.CurrentDisplayMode.Format,
                        Pool.Default
                        );

                    // And get the MipMap surface 0 for the VMR9 filter
                    privateSurface = privateTexture.GetSurfaceLevel(0);
                    unmanagedSurface = privateSurface.GetObjectByValue(DxMagicNumber);

                    // This code path don't need a surface copy.
                    // The client appllication use the same texture the VMR9 filter use.
                    needCopy = false;
                }
                privateTexture.AutoGenerateFilterType = TextureFilter.Anisotropic;
                video.video.Updater.SetSourceSize(new SizeS(textureSize.Width, textureSize.Height != videoSize.Height ? textureSize.Height - 1 : textureSize.Height));
            

            }

        }
        // Helper method to convert interger FourCC into a readable string
        private string FourCCToStr(int fcc)
        {
            byte[] chars = new byte[4];

            if (fcc < 100)
                return fcc.ToString();

            for (int i = 0; i < 4; i++)
            {
                chars[i] = (byte)(fcc & 0xff);
                fcc = fcc >> 8;
            }

            return Encoding.ASCII.GetString(chars);
        }

        #region Members of IVMRSurfaceAllocator9

        public int InitializeDevice(IntPtr dwUserID, ref VMR9AllocationInfo lpAllocInfo, ref int lpNumBuffers)
        {
            lock (allocatorLock)
            {
                Debug.WriteLine(
                    string.Format("{0}x{1} : {2} / {3} / 0x{4:x}", lpAllocInfo.dwWidth, lpAllocInfo.dwHeight,
                                  FourCCToStr(lpAllocInfo.Format), lpAllocInfo.Pool, lpAllocInfo.dwFlags));
                // if format is YUV ? (note : 0x30303030 = "    ")
                if (lpAllocInfo.Format > 0x30303030)
                {
                    // Check if the hardware support format conversion from this YUV format to the RGB desktop format
                    if (
                        !Manager.CheckDeviceFormatConversion(creationParameters.AdapterOrdinal,
                                                             creationParameters.DeviceType, (Format)lpAllocInfo.Format,
                                                             adapterInfo.CurrentDisplayMode.Format))
                    {
                        // If not, refuse this format!
                        // The VMR9 will propose other formats supported by the downstream filter output pin.
                        return D3DERR_INVALIDCALL;
                    }
                }

                try
                {

                    int width = 1;
                    int height = 1;

                    // If hardware require textures to power of two sized
                    if (device.DeviceCaps.TextureCaps.SupportsPower2)
                    {
                        // Compute the ideal size
                        while (width < lpAllocInfo.dwWidth)
                            width = width << 1;
                        while (height < lpAllocInfo.dwHeight)
                            height = height << 1;

                        // notify this change to the VMR9 filter
                        lpAllocInfo.dwWidth = width;
                        lpAllocInfo.dwHeight = height;
                    }

                    videoSize = new Size(lpAllocInfo.dwWidth, lpAllocInfo.dwHeight);
                    textureSize = lpAllocInfo.szNativeSize;
                    // Just in case
                    this.lpAllocInfo = lpAllocInfo;
                    DeleteSurfaces();
                    CreateSurfaces(lpAllocInfo);

                    // This allocator only support 1 buffer.
                    // Notify the VMR9 filter
                    lpNumBuffers = 1;
                }

                catch (DirectXException e)
                {
                    // A Direct3D error can occure : Notify it to the VMR9 filter
                    return e.ErrorCode;
                }
                catch
                {
                    // Or else, notify a more general error
                    return E_FAIL;
                }
            }
            // This allocation is a success
            return 0;
        }

        // Delete surfaces...
        public void DeleteSurfaces()
        {
            lock (allocatorLock)
            {
                if (privateTexture != null)
                {
                    privateTexture.Dispose();
                    privateTexture = null;
                }

                if (privateSurface != null)
                {
                    privateSurface.Dispose();
                    privateSurface = null;
                }

                if (videoSurface != null)
                {
                    videoSurface.Dispose();
                    videoSurface = null;
                }
            }
        }

        public int TerminateDevice(IntPtr dwID)
        {
            DeleteSurfaces();
            return 0;
        }

        public int GetSurface(IntPtr dwUserID, int SurfaceIndex, int SurfaceFlags, out IntPtr lplpSurface)
        {
            lplpSurface = IntPtr.Zero;

            // If the filter ask for an invalid buffer index, return an error.
            if (SurfaceIndex >= 1)
                return E_FAIL;

            lock (allocatorLock)
            {
                if (disposed)
                    return E_FAIL;
                // IVMRSurfaceAllocator9.GetSurface documentation state that the caller release the returned 
                // interface so we must increment its reference count.
                lplpSurface = unmanagedSurface;
                Marshal.AddRef(lplpSurface);
                return 0;
            }
        }

        public  int AdviseNotify(IVMRSurfaceAllocatorNotify9 lpIVMRSurfAllocNotify)
        {
            lock (allocatorLock)
            {
                vmrSurfaceAllocatorNotify = lpIVMRSurfAllocNotify;

                // Give our Direct3D device to the VMR9 filter
                IntPtr unmanagedDevice = device.GetObjectByValue(DxMagicNumber);
                IntPtr hMonitor =  Manager.GetAdapterMonitor(adapterInfo.Adapter);

                return vmrSurfaceAllocatorNotify.SetD3DDevice(unmanagedDevice, hMonitor);
            }
        }

        #endregion

        #region Membres de IVMRImagePresenter9
        
        public int StartPresenting(IntPtr dwUserID)
        {
            if (device == null)
                return E_FAIL;


            return 0;
        }

        public int StopPresenting(IntPtr dwUserID)
        {
            return 0;
        }
        
        
        
        private bool wait, needTexture;

        public bool Wait
        {
            get { return wait; }
            set { wait = value; lock (allocatorLock) { if (hasImage) Monitor.PulseAll(allocatorLock); } }
        }
        
        public int PresentImage(IntPtr dwUserID, ref VMR9PresentationInfo lpPresInfo)
        {
            Monitor.Enter(allocatorLock);
            hasImage = true;
            while (!Closing && !Wait && !needTexture)
            {
                Monitor.Wait(allocatorLock);
            }
            if (Closing || Wait)
            {
                Monitor.Exit(allocatorLock);
                return 0;
            }

            try
            {
                // If YUV mixing is activated, a surface copy is needed
                if (needCopy)
                {
                    // Use StretchRectangle to do the Pixel Format conversion
                    device.StretchRectangle(videoSurface, new Rectangle(Point.Empty, videoSize), privateSurface, new Rectangle(Point.Empty, videoSize), TextureFilter.None);
                }
            }
            catch (DirectXException e)
            {
                // A Direct3D error can occure : Notify it to the VMR9 filter
                return e.ErrorCode;
            }
            catch
            {
                // Or else, notify a more general error
                return E_FAIL;
            }

            hasImage = false;
            Monitor.PulseAll(allocatorLock);
            Monitor.Exit(allocatorLock);

            // This presentation is a success
            return 0;
        }

        #endregion
    }
}